from HmmSubject import HmmSubject;
from HmmGroup import HmmGroup;
from ActGroup import ActGroup;
from ActSubject import ActSubject;
from VarElimination import VarElimination;

class InferenceBB(object):
    def __init__(self, blackBoard):
        self.blackBoard = blackBoard;
        self.graph = blackBoard.graph;
        self.aActs_Time = blackBoard.aActs_Time;
        self.aKeys_Time = blackBoard.aKeys_Time;
        self.aMExNodes = blackBoard.aMExNodes;
        self.knowledgeSrc = blackBoard.knowledgeSrc;
    
    def getViterbi_CHMM(self):
        #Collect all time steps of each subject's timelines
        aTimeSteps = [];
        mSubjects_ByTime = dict(); #Get All diff Subjects involved at each Time Step (To deal Inconsistencies Below)
        aSubjects = self.aKeys_Time.keys();
        aSubjects.sort();
        for subject in aSubjects:
            for sT in self.aKeys_Time[subject]:
                nT = int(sT);
                if not(nT in aTimeSteps):
                    aTimeSteps.append(nT);
                    mSubjects_ByTime[sT] = [subject];
                else:
                    mSubjects_ByTime[sT].append(subject);
        aTimeSteps.sort();
#         print aTimeSteps;
#         print mSubjects_ByTime;
        #Create each Activity by Subject
        mActSubj = dict();
        aSortedActSubj = [];
        for nT in aTimeSteps: #For each Time Step
            sT = str(nT);
            mSubjects_ByTime[sT].sort();
            for sArg in mSubjects_ByTime[sT]: #For each Arg Found
                actSubj = ActSubject(sArg,sT);
                actSubj.getStates(sT, self.aActs_Time);
                mActSubj[(sArg,sT)] = actSubj;
                aSortedActSubj.append(actSubj);
        #Create each CHMM's by Activity Groups
        aActGrps_ByTime = dict();
        nPastT = -1;
        for nT in aTimeSteps:
            sTime = str(nT);
            print "Group time:", nT;
            aActGrps_ByTime[nT] = self.getActGroups(mActSubj, aSortedActSubj, sTime);  # @UnusedVariable
            for actGrp in aActGrps_ByTime[nT]:
                print actGrp.mSubj2StNames;
                if nPastT!=-1:
                    actGrp.setPastActGrps(aActGrps_ByTime[nPastT]);
                [aJntFwd, aMaxFwd_PastInds] = actGrp.getAll_JntFwds();
                print [aJntFwd, aMaxFwd_PastInds];
            nPastT = nT;
        #Get Viterby from these CHMM's
        aTimeSteps_Reversed = list(aTimeSteps);
        aTimeSteps_Reversed.reverse();
        print "\nBest Paths:";
        aSubjPaths = dict();
        for i,nT in enumerate(aTimeSteps_Reversed):
            for actGrp in aActGrps_ByTime[nT]:
#                 bestStates = actGrp.getBestStates();
                nNextT = aTimeSteps_Reversed[i-1];
                aNext_actGrps = aActGrps_ByTime[nNextT] if i>0 else None;
                bestStates = self.get_ConsistentStates(actGrp, aNext_actGrps);
                print bestStates;
                for subject in actGrp.aSubjects: #Format the Best Path for each Subject 
                    iBestSt = bestStates[subject];
                    if not(aSubjPaths.has_key(subject)):
                        aSubjPaths[subject] = [actGrp.mSubj2StNames[subject][iBestSt]];
                    else:
                        aSubjPaths[subject].append(actGrp.mSubj2StNames[subject][iBestSt]);
        for subject in aSubjects:
            aSubjPaths[subject].reverse();
            print subject, aSubjPaths[subject];
            
    def get_ConsistentStates(self, actGrp, aNext_actGrps):
        bestStates = actGrp.getBestStates();
        aBestSt = [];
        for subject in actGrp.aSubjects: #Transform bestStates into Array
            aBestSt.append(bestStates[subject]);
        if actGrp.check_Inconsistency(aBestSt):
            print "Inconsistency Found at", actGrp.sTime, "with", aBestSt;
            bestStates = actGrp.getBest_ConsistentStates(aNext_actGrps);
            ks_ConsistentSt = self.knowledgeSrc.getConsistentStates(actGrp.mSubj2States, actGrp.aJntFwd, [], actGrp.sTime, actGrp.aSubjects); #(aStateSpace, aListConsistentSt, aScores, sTime, aSubjects);
            if ks_ConsistentSt!=None: 
                bestStates = ks_ConsistentSt;
        return bestStates;
            
    def getActGroups(self, mActSubj, aSortedActSubj, sTime):
        aActGrps = [];
        #Group linked Activities (by Subject)
        for actSubj in aSortedActSubj:
            if sTime!=actSubj.sTime: continue; #Ignore ActSubj that don't have States in sTime                
            bInGrp = False;
            for actGrp in aActGrps: #Avoid adding a an actGrp that is already added to the list of groups (aActGrps)
                bInGrp = True if (actSubj in actGrp.aActSubj) else bInGrp;
            if bInGrp: continue;
            #Add All Linked Hmms to a Group
            actGrp = self.getActGrp(actSubj, mActSubj, sTime);
            aActGrps.append(actGrp);
        return aActGrps;
    
    def getActGrp(self, targetActSubj, mActSubj, sTime):
        #Add All Linked Activities (by Subject) to a Group
        actGrp = ActGroup(sTime, self.graph);
        aMoreSubjects = [targetActSubj.sArgSubject];
        while len(aMoreSubjects)>0:
            actGrp.add(mActSubj[(aMoreSubjects[0],sTime)]);
            otherActSubj = mActSubj[(aMoreSubjects[0],sTime)];
            for stNode in otherActSubj.aStates: #Look for Links to Other Hmms (to add in this Group)
                stNode = stNode.rootAct;
                if len(stNode.aOnlySubjects)>1:
                    for subject in stNode.aOnlySubjects:
                        if not(subject in aMoreSubjects) and not(subject in actGrp.aSubjects):          
                            aMoreSubjects.append(subject);
            del aMoreSubjects[0];
        return actGrp;
        
        
            
    def getHmmGroups(self, aHmms, aSortedHmms, sTime):
        aHmmGrps = [];
        #Group linked Hmms
        for hmm in aSortedHmms:
            if not(sTime in hmm.aTimes): continue; #Ignore Hmms that don't have States in sTime                
            bInGrp = False;
            for hmmGrp in aHmmGrps: #Avoid adding a an hmmGrp that is already added to the list of groups (aHmmGrps)
                bInGrp = True if (hmm in hmmGrp.aHmms) else bInGrp;
            if bInGrp: continue;
            #Add All Linked Hmms to a Group
            hmmGrp = self.getHmmGrp(hmm, aHmms, aSortedHmms, sTime);
            aHmmGrps.append(hmmGrp);
        return aHmmGrps;
    
    def getHmmGrp(self, targetHmm, aHmms, aSortedHmms, sTime):
        #Add All Linked Hmms to a Group
        hmmGrp = HmmGroup();
        aMoreSubjects = [targetHmm.sArgSubject];
        while len(aMoreSubjects)>0:
            hmmGrp.add(aHmms[aMoreSubjects[0]]);
            otherHmm = aHmms[aMoreSubjects[0]];
            iT = otherHmm.aTimes.index(sTime);
            for stateN in otherHmm.aStates[iT]: #Look for Links to Other Hmms (to add in this Group)
                stateN = stateN.rootAct;
                if len(stateN.aOnlySubjects)>1:
                    for subject in stateN.aOnlySubjects:
                        if not(subject in aMoreSubjects) and not(subject in hmmGrp.aSubjects):          
#                             if self.knowledgeSrc.getGroupValidation(stateN, "InferenceBB.getHmmGrp")==False: continue;
                            aMoreSubjects.append(subject);
            del aMoreSubjects[0];
        return hmmGrp;
    
    def getLink_This_2_NextNode(self, hmm, hmm_Node, iT):
        bHasLink = True;
        if iT<len(hmm.aTimes)-1: #If it's NOT Last Node, then get Trans (from BestChosen_NextNode to ThisNode)
            nextHmm_Node = hmm.aBestPath[iT+1]; #BestChosen_NextNode
            nextHmm_NodeInfoCPT = self.graph.ndict[nextHmm_Node]; #CPT of BestChosen_NextNode
            if not(hmm_Node in nextHmm_NodeInfoCPT[0]): #If there is NOT link (from BestChosen_NextNode to ThisNode), then Return Zero
                bHasLink = False
        return bHasLink;
    
    def getStateSpace(self, aSortedHmms, sTime):
        aStateSpace = [];
        print "\nState Space:"
        for hmm in aSortedHmms:
            aStates_names = [];
            aStates = [];
            iT = hmm.aTimes.index(sTime);
            for state in hmm.aStates[iT]:
                if not(self.getLink_This_2_NextNode(hmm, state.rootAct, iT)):
                    continue;
                aStates_names.append(state.rootAct.name);
                aStates.append(state.rootAct);
            aStateSpace.append(aStates);
            print hmm.sArgSubject, aStates_names;
        print "\n";
        return aStateSpace;
        
    def getChosenIds(self, aChosenStates):
        sChosenId = "";
        for chosenSt in aChosenStates:
            sChosenId += chosenSt.sId;
        return sChosenId;
    
    def getConsistentStates(self, aSortedHmms, sTime):
        #Get Consistent States
        aStateSpace = self.getStateSpace(aSortedHmms, sTime);
        nMaxSamples = float("inf");
        aRootSt = [];        
        #Get Root level (of Choice Tree), by adding all Children
        iT = aSortedHmms[0].aTimes.index(sTime);
        for childSt in aSortedHmms[0].aStates[iT]:
            aRootSt.append(childSt.rootAct);
        #Get N samples of Consistent State Assignments
        aChosenStates, aSamples = [], [];
        while aRootSt!=[] and len(aSamples)<nMaxSamples:
            for i,hmm in enumerate(aSortedHmms):
                #Get Candidates of this State
                if aChosenStates==[]: #If it's Root Level, then get current root candidate states
                    aChildStates = aRootSt;
                else: #Else, choose among Candidates (Children) of selected State
                    parentSt = aChosenStates[-2] if len(aChosenStates)>=2 else None;
                    tKey = self.getChosenIds(aChosenStates); #(aSortedHmms[i-1].sArgSubject, parentSt);
                    if not(aChosenStates[-1].mChildren.has_key(tKey)):
                        #Initialize & Get Children of last Chosen State
                        iT = hmm.aTimes.index(sTime);
                        aChildStates = self.getChildStates(hmm, iT, aChosenStates);
                        aChosenStates[-1].mChildren[tKey] = aChildStates;
                    else: #Get current Children of last Chosen State
                        aChildStates = aChosenStates[-1].mChildren[(tKey)];
                #Pick Any Random Child State
#                 iRnd = random.randrange(len(aChildStates)); #Stochastic
                iRnd = 0; #Deterministic
                aChosenStates.append(aChildStates[iRnd]);
                
                #If leaf reached (if it's last subject), then mark this choice
                if hmm==aSortedHmms[-1]:
                    #Remove Leaf from Last Chosen State's Children
                    iP, iCh = 2, 1;
                    parentSt = aChosenStates[-iP] if len(aChosenStates)>=iP else None;
                    tKey = self.getChosenIds(aChosenStates[:-1]);
                    iDel = parentSt.mChildren[tKey].index(aChosenStates[-iCh]);
                    del parentSt.mChildren[tKey][iDel];
                    #If that was Last Child, then Go Backwards removing Last Chosen State's Children
                    while parentSt!=None and len(parentSt.mChildren[tKey])==0:
                        iP += 1;
                        iCh = iP-1;
                        parentSt = aChosenStates[-iP] if len(aChosenStates)>=iP else None;
                        if parentSt==None:
                            iDel = aRootSt.index(aChosenStates[0]);
                            del aRootSt[iDel];
                        else:
                            tKey = self.getChosenIds(aChosenStates[:-iP+1]);
                            iDel = parentSt.mChildren[tKey].index(aChosenStates[-iCh]);
                            del parentSt.mChildren[tKey][iDel];
            aSamples.append(aChosenStates);
#             sChosenStates = [];
#             for chosenSt in aChosenStates:
#                 sChosenStates.append(chosenSt.name);
#             print "Adding Sample "+str(len(aSamples))+":", sChosenStates;
            aChosenStates = [];
        print "Consistent States:";
        nMaxScore = float("-inf");
        aMaxConsistentSt = [];
        aListConsistentSt = [];
        aScores = [];
        for k,sample in enumerate(aSamples):
            aConsistentSt = [];
            sConsistentSt = [];
            nScore = 1;
            for i,state in enumerate(sample):
                aConsistentSt.append(state);
                sConsistentSt.append(state.name);
                iT = aSortedHmms[i].aTimes.index(sTime);
                nConsistentFwd = self.getConsistentFwd(state, aSortedHmms[i], iT);
                if nConsistentFwd==-1:
                    nScore = -1;
                if nScore!=-1:
                    nScore *= nConsistentFwd;
            if nScore>=0:
                aListConsistentSt.append(aConsistentSt);
                aScores.append(nScore);
            print k, sConsistentSt, nScore;
            if nScore>=nMaxScore:
                nMaxScore = nScore;
                aMaxConsistentSt = aConsistentSt;
        print "\n";
        #Query the Knowledge Source about Best Consistent State
        aSubjects = [];
        for hmm in aSortedHmms:
            aSubjects.append(hmm.sArgSubject);
        ks_ConsistentSt = self.knowledgeSrc.getConsistentStates(aStateSpace, aListConsistentSt, aScores, sTime, aSubjects);
        if ks_ConsistentSt!=None: 
            aMaxConsistentSt = ks_ConsistentSt;
        return aMaxConsistentSt;
        
    def getConsistentFwd(self, hmm_Node, hmm, iT): #Gets "Second" best Fwd for State(iT+1), whose Past State (hmm_Node) is Consistent
        nPastFwd = hmm_Node.mForward[hmm.sArgSubject];
        nTrans = 1;
        if iT<len(hmm.aTimes)-1: #If it's NOT Last Node, then get Trans (from BestChosen_NextNode to ThisNode)
#             print hmm.aTimes, hmm.aBestPath
            nextHmm_Node = hmm.aBestPath[iT+1]; #BestChosen_NextNode
            nextHmm_NodeInfoCPT = self.graph.ndict[nextHmm_Node]; #CPT of BestChosen_NextNode
            if not(hmm_Node in nextHmm_NodeInfoCPT[0]): #If there is NOT link (from BestChosen_NextNode to ThisNode), then Return Zero
                return -1;
            iParent = (len(nextHmm_NodeInfoCPT[0])-1)-nextHmm_NodeInfoCPT[0].index(hmm_Node); #Reverse index to get correct parent Prob from CPT                
            #If there was another Trans from [Hmm2] (from BestChosen_NextNode to ThisNode_of_Hmm2), 
            #we assume such Hmm2 will take care of its own best Trans from same BestChosen_NextNode.
            iTrans = 2**iParent;
            nTrans = nextHmm_NodeInfoCPT[1][iTrans]; #Trans (from BestChosen_NextNode to ThisNode)
#         print "Fwd:", hmm_Node.mForward[hmm.sArgSubject];
#         print "Trans:", nTrans;
        return nPastFwd*nTrans;
    
    def getChildStates(self, hmm, iT, aChosenStates):
        aChildStates, multSt_And_This = [], None;
        #If There is Any [Multiple] State that Include this Subject, then Choose such state
        for chosenSt in aChosenStates:
            if hmm.sArgSubject in chosenSt.aSubjects:
                multSt_And_This = chosenSt;
        if multSt_And_This!=None:
            #Pick such Child State (that has this same Subject)
            aChildStates = [multSt_And_This];
        #Else, then choose either Single or any Multiple State NOT involving last subjects
        elif multSt_And_This==None:
            #Get Candidate [Children] states NOT involving last subjects
            for childSt in hmm.aStates[iT]:
                bInLastSubjects = False;
                for sArg in childSt.aSubjects: #For each Child's Subject
                    if bInLastSubjects: break;
                    for lastSt in aChosenStates: #Check each last State's subjects
                        if sArg in lastSt.aSubjects:
                            bInLastSubjects = True;
                            break;
                if not(bInLastSubjects):
                    aChildStates.append(childSt.rootAct);
        return aChildStates;
        
    def getMAP(self):
        #Collect all time steps of each subject's timelines
        aTimeSteps = []
        aNodes, aObsNodes = [], [];
        mSubjects_ByTime = dict(); #Get All diff Subjects involved at each Time Step (To deal Inconsistencies Below)
        self.setMExObs();
        for subject in self.aKeys_Time:
            for sT in self.aKeys_Time[subject]:
                for node in self.aActs_Time[(subject,sT)]:
                    if not(node in aNodes):
                        aNodes.append(node.rootAct);
                for nodeME in self.aMExNodes[(subject,sT)]: #Get All Obs (MEx) Nodes
                    if not(node in aNodes):
                        aObsNodes.append(nodeME);
                nT = int(sT);
                if not(nT in aTimeSteps):
                    aTimeSteps.append(nT);
                    mSubjects_ByTime[sT] = [subject];
                else:
                    mSubjects_ByTime[sT].append(subject);
        aTimeSteps.sort();
        #Create & Update each HMM's Subject
        aHmms = dict();
        aSortedHmms = [];
        for nT in aTimeSteps: #For each Time Step
            sT = str(nT);
            mSubjects_ByTime[sT].sort();
            for sArg in mSubjects_ByTime[sT]: #For each Arg Found
                if aHmms.has_key(sArg): #If HMM exists (with this Arg)
                    aHmms[sArg].fillHmm(sT, self.aActs_Time, self.graph);
                else:
                    hmm = HmmSubject(sArg);
                    hmm.fillHmm(sT, self.aActs_Time, self.graph);
                    aHmms[sArg] = hmm;
                    aSortedHmms.append(hmm);
        print "\n\nBest HMM Path:";
        aMAP = self.graph.getMAP();
        aBestPaths = [];
        print aMAP;
        for hmm in aSortedHmms:
            bestPath = hmm.getBestPath_MAP(aMAP, self.graph);
            print "- HMM: ", hmm.sArgSubject, bestPath;
            aBestPaths.append([hmm.sArgSubject, bestPath]);
        print "#Subjects:", len(aSortedHmms);
        print "#Nodes:", len(self.graph.ndict.keys());
        return aBestPaths;
            
    def getMaxProd_VE(self):
        #Collect all time steps of each subject's timelines
        aTimeSteps = []
        aNodes, aObsNodes = [], [];
        mSubjects_ByTime = dict(); #Get All diff Subjects involved at each Time Step (To deal Inconsistencies Below)
        for subject in self.aKeys_Time:
            for sT in self.aKeys_Time[subject]:
                for node in self.aActs_Time[(subject,sT)]:
                    if not(node in aNodes):
                        aNodes.append(node.rootAct);
                for nodeME in self.aMExNodes[(subject,sT)]: #Get All Obs (MEx) Nodes
                    if not(node in aNodes):
                        aObsNodes.append(nodeME);
                nT = int(sT);
                if not(nT in aTimeSteps):
                    aTimeSteps.append(nT);
                    mSubjects_ByTime[sT] = [subject];
                else:
                    mSubjects_ByTime[sT].append(subject);
        aTimeSteps.sort();
        #Create & Update each HMM's Subject
        aHmms = dict();
        aSortedHmms = [];
        for nT in aTimeSteps: #For each Time Step
            sT = str(nT);
            mSubjects_ByTime[sT].sort();
            for sArg in mSubjects_ByTime[sT]: #For each Arg Found
                if aHmms.has_key(sArg): #If HMM exists (with this Arg)
                    aHmms[sArg].fillHmm(sT, self.aActs_Time, self.graph);
                else:
                    hmm = HmmSubject(sArg);
                    hmm.fillHmm(sT, self.aActs_Time, self.graph);
                    aHmms[sArg] = hmm;
                    aSortedHmms.append(hmm);
        print "\n\nBest HMM Path:";
        vElim = VarElimination();
        [aVars, aMAP_VE] = vElim.getInference(self.graph, aNodes, aObsNodes);
        aBestPaths = [];
        for hmm in aSortedHmms:
            bestPath = hmm.getBestPath_VE(aVars, aMAP_VE, self.graph);
            print "- HMM: ", hmm.sArgSubject, bestPath;
            aBestPaths.append([hmm.sArgSubject, bestPath]);
        print "#Subjects:", len(aSortedHmms);
        print "#Nodes:", len(self.graph.ndict.keys());
        return aBestPaths;
#         print [aVars, aMAP_VE];
#         for hmm in aHmms:
#             bestPath = hmm.getBestPath_VE(aMAP_VE, self.graph);
#             print "- HMM: ", hmm.sArgSubject, bestPath;
        
        
    
    def getViterbi(self):
        #Collect all time steps of each subject's timelines
        aTimeSteps = [];
        mSubjects_ByTime = dict(); #Get All diff Subjects involved at each Time Step (To deal Inconsistencies Below)
        for subject in self.aKeys_Time:
            for sT in self.aKeys_Time[subject]:
                nT = int(sT);
                if not(nT in aTimeSteps):
                    aTimeSteps.append(nT);
                    mSubjects_ByTime[sT] = [subject];
                else:
                    mSubjects_ByTime[sT].append(subject);
        aTimeSteps.sort();
#         print aTimeSteps;
#         print mSubjects_ByTime;
        #Create & Update each HMM's Subject
        aHmms = dict();
        aSortedHmms = [];
        for nT in aTimeSteps: #For each Time Step
            sT = str(nT);
            mSubjects_ByTime[sT].sort();
            for sArg in mSubjects_ByTime[sT]: #For each Arg Found
                if aHmms.has_key(sArg): #If HMM exists (with this Arg)
#                     print "**2", sT, iArgFound, sArg, aHmms[iArgFound].sArgSubject;
                    aHmms[sArg].updateHmm(sT, self.aActs_Time, self.graph);
                else:
#                     print "**1", sT, iArgFound, sArg, sArg;
                    hmm = HmmSubject(sArg);
                    hmm.updateHmm(sT, self.aActs_Time, self.graph);
                    aHmms[sArg] = hmm;
                    aSortedHmms.append(hmm);
#         for hmm in aHmms.values():
#             print "\n\n\nHMM: ", hmm.sArgSubject;
#             for nT in range(len(hmm.aStates)):
#                 print "    Time:", hmm.aTimes[nT];
#                 for actM in hmm.aStates[nT]:
#                     print "     - States:", actM.rootAct.name +", Likelihood:", actM.rootAct.aScores[0];
#                     print "        - Transitions:";
#                     if not(actM.rootAct.mMaxPast_NodeId.has_key(hmm.sArgSubject) or actM.rootAct.mMaxPast_NodeId[hmm.sArgSubject]==None):
#                         print "          - None";
#                         continue;
#                     for i,actTrans in enumerate(self.graph.ndict[actM.rootAct][0]):
#                         print "           ", actTrans.name +", Trans:", self.graph.ndict[actM.rootAct][1][2**((len(self.graph.ndict[actM.rootAct][0])-1)-i)];
#                     sBackState = actM.rootAct.mMaxPast_NodeId[hmm.sArgSubject].name if actM.rootAct.mMaxPast_NodeId[hmm.sArgSubject]!=None else "None";
#                     print "        - Back-State:", sBackState;
#                     print "        - Forward:", actM.rootAct.mForward[hmm.sArgSubject];
#                 print "     - Max State:", hmm.aMaxSts[nT].rootAct.name;
#                 print "     - Max Prob:", hmm.aProbSt[nT];
        print "\n\nBest HMM Path:";
        #Fix (temporary Patch) the consistency issue of HMM-A choosing Act(A,B) and HMM-B choosing Act(A)
        for hmm in aSortedHmms:
            hmm.getBestPath();
            print hmm.sArgSubject, hmm.getBestPath_Viterbi();
        aTimeVals = list(aTimeSteps);
        aTimeVals.reverse();
        for nT in aTimeVals: #For each Time Step
            sT = str(nT);
            for subject in mSubjects_ByTime[sT]: #For each HMM involved in this TimeStep
                if aHmms[subject].mMultArgs.has_key(sT):
                    #Check if Exists Inconsistency from this Hmm
                    hmm = aHmms[subject];
                    hmmBestAct = hmm.mMultArgs[sT];
                    bInconsistent = False;
                    for otherSubject in hmmBestAct.aSubjects: #For each Subject different from this Hmm (from MultiArgs Activity)
                        otherHmm = aHmms[otherSubject];
                        bHasOneArg = aHmms[otherSubject].mMultArgs.has_key(sT)==False;
                        bIsDifferentAct = hmmBestAct!=otherHmm.mMultArgs[sT] if bHasOneArg==False else True;
                        if otherSubject!=hmm.sArgSubject and (bHasOneArg or bIsDifferentAct):
                            bInconsistent = True;
                            break;
                    if bInconsistent:
                        sTime = hmmBestAct.aTimes[-1];
                        hmmGrp = self.getHmmGrp(hmm, aHmms, aSortedHmms, sTime);
                        print "\nInconsistency found in Time:", sTime;
                        print "Group Formed:", hmmGrp.aSubjects;
                        aBestConsistentSt = self.getConsistentStates(hmmGrp.aHmms, sTime);
                        print "Hmms to Change:";
                        for i,hmmItem in enumerate(hmmGrp.aHmms):
                            iT = hmmItem.aTimes.index(sTime);
                            if hmmItem.aBestPath[iT]!=aBestConsistentSt[i]:
                                print hmmItem.sArgSubject, hmmItem.getBestPath_Viterbi();
                                hmmItem.mBestPath[sTime] = aBestConsistentSt[i];
                                hmmItem.getBestPath();
                        print "\nHmms Updated:"
                        for hmm in aSortedHmms:
                            hmm.getBestPath();
                            print hmm.sArgSubject, hmm.getBestPath_Viterbi();
        #Print Final Results of BestPath
        print "\n";
        aBestPath = [];
        for hmm in aSortedHmms:
            bestPath = hmm.getBestPath_Viterbi();
            print "- HMM: ", hmm.sArgSubject, bestPath;            
            aBestPath.append([hmm.sArgSubject, bestPath]);
#         print "#Subjects:", len(aHmms);
#         print "#Nodes:", len(self.graph.ndict.keys());
#         print "\n";
#         aHmmGrps = self.getHmmGroups(aHmms, aSortedHmms, "64");
#         self.getConsistentStates(aHmmGrps[0].aHmms, "64");
        return aBestPath;
            
    def getSecondSubject(self, hmmSubject, aSubjects):
        for subject in aSubjects:
            if subject!=hmmSubject and subject!="*":
                return subject;
        return None;
            
    def getConsistentNodes(self, multHmm, singHmm, sTime, aHmms):
        iMult = multHmm.aTimes.index(sTime);
        iSing = singHmm.aTimes.index(sTime);
        singHmm_Node  = singHmm.aBestPath[iSing];
        multHmm_Node = multHmm.aBestPath[iMult];
#         print sTime;
#         print multHmm.aBestPath[iMult].name;
#         print singHmm.aBestPath[iSing].name;
#         print multHmm.aTimes[iMult+1];
#         print multHmm.aBestPath[iMult+1].name;
#         print singHmm.aBestPath[iSing+1].name;
        #If Both have same Subjects, but different Activities, then: Set Most Likely Act on both
        nArgs_Mult_Node = self.getNumArgs(multHmm_Node);
        nArgs_Sing_Node = self.getNumArgs(singHmm_Node);
        bSameArgs = self.getSameArgs(multHmm_Node, singHmm_Node, nArgs_Mult_Node, nArgs_Sing_Node);
        if nArgs_Mult_Node>1 and nArgs_Sing_Node>1 and bSameArgs:
            mult_JntFwd1 = self.getMultHmm_JntFwd(multHmm, singHmm, iMult, iSing);
            mult_JntFwd2 = self.getMultHmm_JntFwd(singHmm, multHmm, iSing, iMult);
            if mult_JntFwd1 >= mult_JntFwd2:
                return [singHmm_Node, None];
            else:                
                return [None, multHmm_Node];
            print "bothMult-mult_JntFwd:", mult_JntFwd1;
            print "bothMult-sing_JntFwd:", mult_JntFwd2;
        elif nArgs_Mult_Node>1 and nArgs_Sing_Node>1 and not(bSameArgs):
            #Else if Both have Multiple, but different Subjects, then: Set Most Likely between (Same Multiple Act on both) or (Otherwise set only Primary into its Single)
            print "\ngetMultHmm_JntFwd:";
            mult_JntFwd = self.getMultHmm_JntFwd(multHmm, singHmm, iMult, iSing); #Score of Same Multiple Act on Both
            print "mult_JntFwd:", mult_JntFwd;
            print "\ngetSingHmm_JntFwd:";
            [sing_JntFwd, singHmm_Node] = self.getSingHmm_JntFwd(multHmm, singHmm, iMult, iSing); #Score of Both with own Singles
            print "sing_JntFwd:", sing_JntFwd;
            print "\ngetMultHmm_JntFwd - Other:";
            sOtherSubject = self.getSecondSubject(singHmm.sArgSubject, singHmm.aBestPath[iSing].aSubjects);
            otherHmm = aHmms[sOtherSubject];
            iOther = otherHmm.aTimes.index(sTime);
            other_Mult_JntFwd = self.getMultHmm_JntFwd(singHmm, otherHmm, iSing, iOther); #Score of Same Multiple Act on Both (Second & Other) Subjects
            print "other_Mult_JntFwd:", mult_JntFwd;
            if sing_JntFwd >= mult_JntFwd or (sing_JntFwd<mult_JntFwd and mult_JntFwd<=other_Mult_JntFwd):
                return [singHmm_Node, None]; #Set only Primary into its Single
            else:
                return [None, multHmm_Node]; #Set Same Multiple Act on both
        elif nArgs_Mult_Node>1 and nArgs_Sing_Node==1:
            #Else if one has Multiple subjects and the other only One, then: Set Most Likely between (Same Multiple Act on both) or (Both with own Singles)
            print "\ngetMultHmm_JntFwd:";
            mult_JntFwd = self.getMultHmm_JntFwd(multHmm, singHmm, iMult, iSing); #Score of Same Multiple Act on Both
            print "mult_JntFwd:", mult_JntFwd;
            print "\ngetSingHmm_JntFwd:";
            [sing_JntFwd, singHmm_Node] = self.getSingHmm_JntFwd(multHmm, singHmm, iMult, iSing); #Score of Both with own Singles
            print "sing_JntFwd:", sing_JntFwd;
            if sing_JntFwd >= mult_JntFwd:
                return [singHmm_Node, None]; #Set Both with own Singles
            else:
                return [None, multHmm_Node]; #Set Same Multiple Act on both
        
    def getSameArgs(self, multHmm_Node, singHmm_Node, nArgs_Mult_Node, nArgs_Sing_Node):
        if nArgs_Mult_Node!=nArgs_Sing_Node:
            return False;
        for sArg in multHmm_Node.aSubjects:
            if not(sArg in singHmm_Node.aSubjects) and sArg!="*":
                return False;
        return True;
        
    def getMultHmm_JntFwd(self, multHmm, singHmm, iMult, iSing):
        jntHmm_Node  = multHmm.aBestPath[iMult];
        print jntHmm_Node.name;
        singFwd = jntHmm_Node.mForward[singHmm.sArgSubject];
        multFwd = jntHmm_Node.mForward[multHmm.sArgSubject];
        print "Fwd-A:", jntHmm_Node.mForward[singHmm.sArgSubject];
        print "Fwd-B:", jntHmm_Node.mForward[multHmm.sArgSubject];
        nTransP1, nTransP2 = 1, 1;
        if iMult<len(multHmm.aTimes)-1:
            multHmm_Node = multHmm.aBestPath[iMult+1];
            multHmm_NodeInfoCPT = self.graph.ndict[multHmm_Node]; #CPT
            iParent = (len(multHmm_NodeInfoCPT[0])-1)-multHmm_NodeInfoCPT[0].index(jntHmm_Node); #Reverse index to get correct parent Prob from CPT
            iTrans = 2**iParent;
            nTransP1 = multHmm_NodeInfoCPT[1][iTrans];
        if iSing<len(singHmm.aTimes)-1:
            singHmm_Node = singHmm.aBestPath[iSing+1];
            singHmm_NodeInfoCPT = self.graph.ndict[singHmm_Node]; #CPT
            iParent = (len(singHmm_NodeInfoCPT[0])-1)-singHmm_NodeInfoCPT[0].index(jntHmm_Node); #Reverse index to get correct parent Prob from CPT
            iTrans = 2**iParent;
            nTransP2 = singHmm_NodeInfoCPT[1][iTrans];
        print "Trans-B:", nTransP1;
        print "Trans-A:", nTransP2;
        return multFwd*nTransP1 * singFwd*nTransP2;
        
    def getSingHmm_JntFwd(self, multHmm, singHmm, iMult, iSing):
        singHmm_NodePast  = singHmm.aBestPath[iSing];
        multHmm_NodePast = multHmm.aBestPath[iMult];
        singHmm_Node = singHmm_NodePast;
        singFwd = singHmm_NodePast.mForward[singHmm.sArgSubject];
        multFwd = multHmm_NodePast.mForward[multHmm.sArgSubject];
        print "Fwd-A:", singHmm_NodePast.mForward[singHmm.sArgSubject];
        print "Fwd-B:", multHmm_NodePast.mForward[multHmm.sArgSubject];
        nTransP1, nTransP2 = 1, 1;
        if iSing<len(singHmm.aTimes)-1:
            singHmm_Node = singHmm.aBestPath[iSing+1];
            singHmm_NodeInfoCPT = self.graph.ndict[singHmm_Node]; #CPT
            iParent = (len(singHmm_NodeInfoCPT[0])-1)-singHmm_NodeInfoCPT[0].index(singHmm_NodePast); #Reverse index to get correct parent Prob from CPT
            iTrans = 2**iParent;
            nTransP1 = singHmm_NodeInfoCPT[1][iTrans];
        if iMult<len(multHmm.aTimes)-1:
            multHmm_Node = multHmm.aBestPath[iMult+1];
            multHmm_NodeInfoCPT = self.graph.ndict[multHmm_Node]; #CPT
            [nMax_TransFwd, singHmm_Node, nTransP2, multFwd] = self.getMaxFwd(multHmm_NodeInfoCPT, multHmm.sArgSubject);
            print "Fwd-B:", nMax_TransFwd;
            print "MaxNode-B:", singHmm_Node.name;
        print "Trans-A:", nTransP2;
        print "Trans-B:", nTransP1;
        return  [singFwd*nTransP1 * multFwd*nTransP2, singHmm_Node];

    def getMaxFwd(self, nodeInfoCPT, sActor):
        nMax_TransFwd, maxPastAct, maxTrans, maxFwd = float("-inf"), None, float("-inf"), float("-inf");
        for i,pastAct in enumerate(nodeInfoCPT[0]): #For each PastAct
            if self.getNumArgs(pastAct)>1: continue;
            iParent = (len(nodeInfoCPT[0])-1)-i; #Reverse index to get correct parent Prob from CPT
            iTrans = 2**iParent;
            nTransP = nodeInfoCPT[1][iTrans];
    #         nTransFwd = math.log(nTransP) + pastAct.nForward;
            nTransFwd = nTransP * pastAct.mForward[sActor];
            if nTransFwd>=nMax_TransFwd:
                nMax_TransFwd = nTransFwd;
                maxPastAct = pastAct;
                maxTrans = nTransP;
                maxFwd = pastAct.mForward[sActor];
        return [nMax_TransFwd, maxPastAct, maxTrans, maxFwd];
    
    def getNumArgs(self, actNode):
        aArgs = [];
        for sArg in actNode.aSubjects: #For each Arg (in this Act)
            if not(sArg in aArgs) and sArg!="*":
                aArgs.append(sArg);
        return len(aArgs);
    
    def getHmms(self, bSmooth=False):
        aHmms = [];
        aArgs = [];
        self.setMExObs();
        if bSmooth: self.setEvObs();
        for sT in self.aKeys_Time: #For each Time Step
            if not(bSmooth): self.setEvObs(float(sT));
            for actM in self.aActs_Time[sT]: #For each Activity
                for sArg in actM.rootAct.aSubjects: #For each Arg (in this Act)
                    if not(sArg in aArgs) and sArg!="*":
                        aArgs.append(sArg);
            for sArg in aArgs: #For each Arg Found
                iArgFound = -1;
                for i in range(len(aHmms)): #For each HMM
                    if aHmms[i].sArgSubject == sArg:
                        iArgFound = i;
                        break;
                if iArgFound>=0: #If HMM exists (with this Arg)
                    aHmms[iArgFound].updateHmm(sT, self.aActs_Time, self.graph);
                else:
                    hmm = HmmSubject(sArg);
                    hmm.updateHmm(sT, self.aActs_Time, self.graph);
                    aHmms.append(hmm);
        for hmm in aHmms:
            print "\n\n\nHMM: ", hmm.sArgSubject;
            for nT in range(len(hmm.aStates)):
                print "    Time:", hmm.aTimes[nT];
#                 for actM in hmm.aStates[nT]:
#                     print "     - States:", actM.rootAct.name;
                print "     - Max State:", hmm.aMaxSts[nT].rootAct.name;
                print "     - Max Prob:", hmm.aProbSt[nT];
        self.removeEvObs();
        self.removeMExObs();
#             print hmm.aStates;
#             print hmm.aTimes;

    def getProbSeq(self, aActCodes, bSmooth=False):
        aActNodes = [];
        nProbSeq = 1.0;
        self.setMExObs();
        if bSmooth: self.setEvObs();
        #Get Act Nodes
        for actCode in aActCodes:
            if not(self.graph.idndict.has_key(actCode)):
                print "Activity " + actCode + " was Not found.";
                return -1;
            aActNodes.append(self.graph.idndict[actCode]);
        #Get Probability of Sequence
        initT = self.aKeys_Time.index(aActCodes[0].split(".")[1]);
        pastAct = None;
        for i,actM in enumerate(aActNodes):
            #Clamp last biggest Post
            if i>0:
                for actM2 in self.aActs_Time[self.aKeys_Time[initT+i-1]]:
                    self.graph.addobs(actM2.rootAct, False);
                self.graph.addobs(pastAct, True);
            if not(bSmooth): self.setEvObs(float(actM.aTimes[-1]));
            nProbSeq *= self.graph.postprob([actM])[0];
            pastAct = actM;
            #Unclamp last Posts
            if i>0:
                for actM2 in self.aActs_Time[self.aKeys_Time[initT+i-1]]:
                    self.graph.removeObs(actM2.rootAct);
        self.removeEvObs();
        return nProbSeq;            
    
    def getMaxStProb(self, sT):
        aProbSt = [];
        nActs = len(self.aActs_Time[sT]);
        for i in range(nActs):
#             print self.aActs_Time[sT][i].rootAct.name;
            aProbSt.append(self.graph.postprob([self.aActs_Time[sT][i].rootAct])[0]);
#             sVals = bin(2**i)[2:].zfill(nActs);
#             aProbSt.append(self.getStateProb(sT, sVals));
#         print aProbSt;
        nMaxP = max(aProbSt);
        iMaxP = aProbSt.index(nMaxP);
        return [iMaxP, nMaxP];
    
    def getClosest_Time(self, nTime):
        sClosest_Time = "";
        nClosestDist = float("inf");
        for sT in self.aKeys_Time:
            nDist = abs(nTime-int(sT));
            if nDist<nClosestDist:
                nClosestDist = nDist;
                sClosest_Time = sT;
        return sClosest_Time;
    
    def getStatePosts(self, nTime, nMaxT_Ev=float("inf")):
        self.blackBoard.makeUsrSegment(nTime);
        sT = str(nTime);
#         sT = self.getClosest_Time(nTime);
        self.setEvObs(nMaxT_Ev);
        self.setMExObs(float(0), float(sT));
        aNodes = [];
        for actM in self.aActs_Time[sT]:
            aNodes.append(actM.rootAct);
        aPosts = self.graph.postprob(aNodes);
#         self.removeEvObs();
#         self.removeMExObs();
#         self.removeUsrSegment(nTime);
        return [aNodes, aPosts, sT];
    
    def getJointProb(self, nTime, nMaxT_Ev=float("inf")):
        sT = self.getClosest_Time(nTime);
        nNodes = len(self.aActs_Time[sT]);
        aProbs = [];
        aNodes = [];
        for actM in self.aActs_Time[sT]:
            aNodes.append(actM.rootAct);
        for i in range(2**nNodes):
            sBin = bin(i)[2:].zfill(nNodes);
            nProb = self.getStateProb(nTime, sBin, nMaxT_Ev);
            aProbs.append(nProb);
        return [aNodes, aProbs, sT];
    
    def getStateProb(self, nTime, sVals, nMaxT_Ev=float("inf")):
        sT = self.getClosest_Time(nTime);
        self.setEvObs(nMaxT_Ev);
        self.setMExObs(float(sT), float(sT));
        nProbSt = 1.0;
        if len(sVals)!=len(self.aActs_Time[sT]):
            print "Error: Number or State Values for time [", sT, "] must be ", len(self.aActs_Time[sT]);
        for i,act in enumerate(self.aActs_Time[sT]):
            actNode = act.rootAct;
            bVal = bool(int(sVals[i]));
            if bVal:
                nProbSt *= self.graph.postprob([actNode])[0];
            else:
                nProbSt *= (1-self.graph.postprob([actNode])[0]);
            self.graph.addobs(actNode, bVal);
        for actM in self.aActs_Time[sT]:
            self.graph.removeObs(actM.rootAct);
        self.removeEvObs();
        self.removeMExObs();
        return nProbSt;
    
    def setEvObs(self, nMaxT=float("inf")):
        #Set ALL Evidence Nodes
        for sKeyT in self.aActs_Time:
            if float(sKeyT)>nMaxT: continue;
            for actM in self.aActs_Time[sKeyT]:
                for childN in actM.aChildNodes:
#                     print "Adding Evidence. sId:", childN.sId;
                    self.graph.addobs(childN, True);                    
    
    def removeEvObs(self):
        #Set ALL Evidence Nodes
        for sKeyT in self.aActs_Time:
            for actM in self.aActs_Time[sKeyT]:
                for childN in actM.aChildNodes:
                    self.graph.removeObs(childN);
    
    def setMExObs(self, nMinT=0, nMaxT=float("inf")):
        #Set ALL MEx Nodes
        for sKeyT in self.aMExNodes:
            for nodeME in self.aMExNodes[sKeyT]:
                if float(sKeyT)<nMinT or float(sKeyT)>nMaxT: continue;
#                 print "Adding MEx Evidence:", nodeME.name;
                self.graph.addobs(nodeME, True);                    
    
    def removeMExObs(self):
        #Unset ALL MEx Nodes
        for sKeyT in self.aMExNodes:
            for nodeME in self.aMExNodes[sKeyT]:
                self.graph.removeObs(nodeME);
    
    def getMaxProbSeq(self, sTimes=[]):
        if sTimes==[]: sTimes = self.aKeys_Time;
